﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using System.Linq;
using System.Web;
using Kaleida.ServiceMonitor.Model.Email;
using Kaleida.ServiceMonitor.Model.Runtime;

namespace Kaleida.ServiceMonitor.Model
{
    internal class NotificationMessageBuilder
    {
        private const string HtmlColourSuccess = "#0A0";
        private const string HtmlColourFailure = "#A00";
        private const string HtmlColourFailureFresh = "#D00";

        public NotificationMessage BuildSummary(Monitor monitor, OperationStatisticsMap statsSinceLastSummary)
        {
            var html = new HtmlMessageBuilder();

            AppendInstanceInformation(html);
            AppendMonitoringPeriod(html, monitor);
            AppendModuleSummary(html, monitor);

            var newColour = monitor.CompiledModule.ColourationStrategy.GetNotificationColour(monitor.State);
            html.AddRawFormat("<p>The state is currently {0}</p>", newColour.ToSummaryHtml());

            AppendErrorSummary(html, monitor.State);

            AppendCombinedAvailabilitySummary(html, statsSinceLastSummary, "Summary <span style='font-size:smaller'>(since last report)</span>".AsRawHtml());
            AppendAvailabilityTable(html, statsSinceLastSummary, "Availability <span style='font-size:smaller'>(since last report)</span>".AsRawHtml());
            AppendPerformanceTable(html, statsSinceLastSummary, "Performance <span style='font-size:smaller'>(since last report)</span>".AsRawHtml());

            AppendCombinedAvailabilitySummary(html, monitor.Statistics, "Summary <span style='font-size:smaller'>(since monitoring began)</span>".AsRawHtml());
            AppendAvailabilityTable(html, monitor.Statistics, "Availability <span style='font-size:smaller'>(since monitoring began)</span>".AsRawHtml());
            AppendPerformanceTable(html, monitor.Statistics, "Performance <span style='font-size:smaller'>(since monitoring began)</span>".AsRawHtml());

            AppendRecentResponses(html, monitor);
            AppendRecentErrors(html, monitor);

            var subject = string.Format("Summary of '{0}'", monitor.CompiledModule.Name);
            return new NotificationMessage(subject, html.ToString());
        }

		public NotificationMessage BuildThresholdChangeSummary(Monitor monitor, Color oldColour)
        {
            var html = new HtmlMessageBuilder();

            AppendInstanceInformation(html);
            AppendMonitoringPeriod(html, monitor);
            AppendModuleSummary(html, monitor);

            var newColour = monitor.CompiledModule.ColourationStrategy.GetNotificationColour(monitor.State);
            html.AddRawFormat("<p>The state colour has changed from {0} to {1}</p>", oldColour.ToSummaryHtml(), newColour.ToSummaryHtml());

            AppendErrorSummary(html, monitor.State);

            AppendCombinedAvailabilitySummary(html, monitor.Statistics, "Summary".HtmlEncode());
            AppendAvailabilityTable(html, monitor.Statistics, "Availability".HtmlEncode());
            AppendPerformanceTable(html, monitor.Statistics, "Performance".HtmlEncode());
            AppendRecentResponses(html, monitor);
            AppendRecentErrors(html, monitor);

            var subject = string.Format("{0} changed from {1} to {2}", monitor.CompiledModule.Name, oldColour.Name, newColour.Name);
            return new NotificationMessage(subject, html.ToString());
        }

        private static void AppendMonitoringPeriod(HtmlMessageBuilder html, Monitor monitor)
        {
            var stats = monitor.Statistics.Combined;
            html.AddRawFormat("Script Module <b>{0}</b> has been running since {1:G}", monitor.CompiledModule.Name.HtmlEncode(), stats.StartTime);
        }
        
        private static void AppendModuleSummary(HtmlMessageBuilder html, Monitor monitor)
        {
            var summary = HttpUtility.HtmlEncode(monitor.CompiledModule.BuildSummary()).Replace("\r\n", "<br/>").AsRawHtml();
            html.AddRawFormat("<div style='color: #666; border: 1px double #666; font-size: smaller; padding: 6px; margin: 4px;'>{0}</div>", summary);
        }

        private static void AppendInstanceInformation(HtmlMessageBuilder html)
        {
            html.AddRawFormat("<p><i>Message sent at {0:G} from <a href='https://www.rightcalc.com/blog/servicemon/'>ServiceMon</a> (v{1}), running on {2}</i></p>", DateTime.Now, ServiceMonApplication.VersionNumber.ToString().HtmlEncode(), Dns.GetHostName().HtmlEncode());
        }

        private static void AppendRecentResponses(HtmlMessageBuilder html, Monitor monitor)
        {
            var recentResponses = monitor.State.RecentResponses;
            html.AddHeading(string.Format("Recent Responses (maximum of {0})", recentResponses.MaximumItemCount).HtmlEncode());

            var bullets = recentResponses.GetItemsNewestFirst().Select(FormatResponse);

            html.AddBullets(bullets);
        }

        private static void AppendRecentErrors(HtmlMessageBuilder html, Monitor monitor)
        {
            var recentErrors = monitor.State.RecentFailures;
            html.AddHeading(string.Format("Recent Errors (maximum of {0})", recentErrors.MaximumItemCount).HtmlEncode());

            var bullets = recentErrors.GetItemsNewestFirst().Select(FormatResponse);

            html.AddBullets(bullets);
        }

        private static IHtmlString FormatResponse(RequestResult i)
        {
            var htmlColor = (i is RequestErrorResult) ? "red" : "black";
            return string.Format("<span style='color:{0}'>{1}</span>", htmlColor, i.ToListItemText().HtmlEncode()).AsRawHtml();
        }

        private static void AppendAvailabilityTable(HtmlMessageBuilder html, OperationStatisticsMap stats, IHtmlString heading)
        {
            html.AddHeading(heading);

            AppendStatisticsApplicableDateRange(html, stats);

            var availabilityTable = new BasicTable();
            availabilityTable.AddHeaderRow(new[] { "Operation", "Requests", "Successful", "Failed", "Success Rate" });
            foreach (var o in stats.Operations)
            {
                var successRate = o.GetSuccessRate();
                var successColor = o.SuccessCount > 0 ? HtmlColourSuccess : "#000";
                var failureColor = o.FailureCount > 0 ? HtmlColourFailure : "#000";

                availabilityTable.AddRow(new[]
                                             {
                                                 o.Description.HtmlEncode(),
                                                 o.ProcessedCount.ToString("0").HtmlEncode(),
                                                 string.Format("<span style='color:{0}'>{1:0}</span>", successColor, o.SuccessCount).AsRawHtml(),
                                                 string.Format("<span style='color:{0}'>{1:0}</span>", failureColor, o.FailureCount).AsRawHtml(),
                                                 (successRate == null ? "" : (successRate.Value * 100).ToString("N3") + "%").HtmlEncode()
                                             });
            }

            html.AddTable(availabilityTable);
        }

        private static void AppendStatisticsApplicableDateRange(HtmlMessageBuilder html, OperationStatisticsMap stats)
        {
            html.AddLine(string.Format("({0} to {1})", stats.Combined.StartTime, DateTime.Now).HtmlEncode());
        }

        private static void AppendCombinedAvailabilitySummary(HtmlMessageBuilder html, OperationStatisticsMap stats, IHtmlString heading)
        {
            html.AddHeading(heading);
            AppendStatisticsApplicableDateRange(html, stats);
            html.AddRawFormat("<b>{0}</b> Request{1} have been sent and received. ", stats.Combined.ProcessedCount, stats.Combined.ProcessedCount == 1 ? "" : "s");
            
            var successColor = stats.Combined.SuccessCount > 0 ? HtmlColourSuccess : "#000";
            var failureColor = stats.Combined.FailureCount > 0 ? HtmlColourFailure : "#000";

            html.AddBullets(new[]
                                {
                                    string.Format("<span style='color:{0}'><b>{1}</b> {2} successful</span>", successColor, stats.Combined.SuccessCount, stats.Combined.SuccessCount == 1 ? "was" : "were").AsRawHtml(),
                                    string.Format("<span style='color:{0}'><b>{1}</b> failed</span>", failureColor,stats.Combined.FailureCount).AsRawHtml()
                                });

        	var successRate = stats.Combined.GetSuccessRate();
            html.AddRawFormat("<p>The current success rate is <b>{0}%</b></p>", successRate != null ? successRate.PercentValue.ToString("N3") : "n/a");
        }

        private static void AppendPerformanceTable(HtmlMessageBuilder html, OperationStatisticsMap stats, IHtmlString heading)
        {
            var percentilePercentages = new[] { 90m, 99m, 99.9m, 99.99m };

            html.AddHeading(heading);

            html.AddRaw("<table>");

            html.AddRaw("<tr>");
            html.AddRaw("<th style='border: 0'></th>");
            html.AddRaw("<th style='border: 0'></th>");
            html.AddRaw("<th style='border: 0'></th>");
            html.AddRaw("<th colspan='4' style='border: 1px solid black'>Percentiles</th>");
            html.AddRaw("<th style='border: 0'></th>");
            html.AddRaw("</tr>");

            html.AddRaw("<tr>");
            html.AddRaw("<th>Operation</th>");
            html.AddRaw("<th>Minimum</th>");
            html.AddRaw("<th>Mean</th>");
            html.AddRaw("<th>90th</th><th>99th</th><th>99.9th</th><th>99.99th</th>");
            html.AddRaw("<th>Maximum</th>");
            html.AddRaw("</tr>");
            
            foreach(var o in stats.Operations)
            {
                var percentiles = o.GetResponseTimePercentiles(percentilePercentages);

                html.AddRaw("<tr>");

                html.AddRawFormat("<td>{0}</td>", o.Description.HtmlEncode());
                html.AddRaw(GetPerformanceCellContentHtml(o.MinimumProcessingTime).ToHtmlString());
                html.AddRaw(GetPerformanceCellContentHtml(o.MeanProcessingTime).ToHtmlString());

                foreach (var percentile in percentiles)
                {
                    html.AddRaw(GetPerformanceCellContentHtml(percentile.Value).ToHtmlString());
                }

                html.AddRaw(GetPerformanceCellContentHtml(o.MaximumProcessingTime).ToHtmlString());
                html.AddRaw("</tr>");
            }

            html.AddRaw("</table>");           
        }

        private static void AppendErrorSummary(HtmlMessageBuilder html, MonitorState monitorState)
        {
            var firstError = monitorState.FirstFailureOrDefault;
            var latestError = monitorState.LatestFailureOrDefault;

            if (firstError != null && latestError != null)
            {
                var bulletLines = BuildErrorSummaryList(firstError, latestError);
                html.AddBullets(bulletLines);
            }
        }

        private static IEnumerable<IHtmlString> BuildErrorSummaryList(RequestErrorResult firstError, RequestErrorResult latestError)
        {
            if (latestError != firstError)
            {
                return new[]
                                  {
                                      string.Format("<span style='color:{0}'>First error:  {1}</span>", HtmlColourFailure, firstError.ToString().HtmlEncode()).AsRawHtml(),
                                      string.Format("<span style='color:{0}'>Latest error: {1}</span>", HtmlColourFailureFresh, latestError.ToString().HtmlEncode()).AsRawHtml()
                                  };
            }
            return new[] { string.Format("<span style='color:#D00'>Error details: {0}</span>", latestError.ToString().HtmlEncode()).AsRawHtml() };
        }

        private static IHtmlString GetPerformanceCellContentHtml(TimeSpan? value)
        {
            if (value == null)
                return "<td>n/a</td>".AsRawHtml();

            var text = string.Format("{0}&nbsp;ms", ((int)value.Value.TotalMilliseconds).ToString("N0")).AsRawHtml();
            var colour = GetHtmlColourForResponseTime(value.Value);
            return string.Format("<td style='background-color: {0};'>{1}</td>", colour, text).AsRawHtml();
        }

        private static string GetHtmlColourForResponseTime(TimeSpan value)
        {
            // thresholds based on http://www.useit.com/alertbox/timeframes.html

            if(value.TotalMilliseconds <= Math.Pow(10, 2)) // within 100ms is considered instant by humans
                return Color.FromArgb(0xF7, 0xFF, 0xF7).ToHtml();

            if(value.TotalMilliseconds <= Math.Pow(10, 2.5))
                return Color.FromArgb(0xFA, 0xFF, 0xF0).ToHtml();

            if(value.TotalMilliseconds <= Math.Pow(10, 3))// within 1 sec is acceptable within "flow"
                return Color.FromArgb(0xFF, 0xFF, 0xDD).ToHtml();

            if (value.TotalMilliseconds <= Math.Pow(10, 3.5))
                return Color.FromArgb(0xFF, 0xFF, 0xAA).ToHtml();

            if (value.TotalMilliseconds <= Math.Pow(10, 4)) // 10 secs is the limits of human attention span
                return Color.FromArgb(0xFF, 0xEE, 0x99).ToHtml();

            if (value.TotalMilliseconds <= Math.Pow(10, 4.5))
                return Color.FromArgb(0xFF, 0xDD, 0x77).ToHtml();

            return Color.FromArgb(0xFF, 0xCC, 0xBB).ToHtml();
        }
    }
}
